﻿// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
//
// This file is part of SharpMap.
// SharpMap is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// SharpMap is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with SharpMap; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 

using System;
using System.Runtime.Serialization;
using System.Collections.Generic;
//using System.Text;

namespace Petra.Gis.ReferenceSystemServices
{
  /// <summary>
  /// The Spheroid defines the standard information stored with ellipsoid objects.
  /// </summary>
  [DataContract(Name="SPHEROID")]
  public partial class Spheroid
  {

    //private Metadata _metadata;
    //private double _eccentricity;
    protected double _eccentricitySquared;


    /// <summary>
    /// xaml constr.
    /// </summary>
    public Spheroid() { }





    /// <summary>
    /// Initializes a new instance of an Ellipsoid
    /// </summary>
    /// <param name="semiMajorAxis">Semi major axis</param>
    /// <param name="semiMinorAxis">Semi minor axis</param>
    /// <param name="inverseFlattening">Inverse flattening</param>
    /// <param name="isIvfDefinitive">Inverse Flattening is definitive for this ellipsoid (Semi-minor axis will be overridden)</param>
    /// <param name="axisUnit">Axis unit</param>
    /// <param name="metadata">this object metadata</param>
    public Spheroid(
      double semiMajorAxis,
      double semiMinorAxis,
      DistanceUnit axisUnit, Metadata metadata)
      {
        if (metadata == null)
          throw new ArgumentException("invalid metadata");

      SemiMajorAxis = semiMajorAxis;
      AxisUnit = axisUnit;
      //if (isIvfDefinitive)
      //  _SemiMinorAxis = (1.0 - (1.0 / _InverseFlattening)) * semiMajorAxis;
      //else
      //  _SemiMinorAxis = semiMinorAxis;
    }



    //public Ellipsoid(string name, Distance equatorialRadius, Distance polarRadius)
    //{
    //    this.pName = name;
    //    this.pEquatorialRadius = equatorialRadius;
    //    this.pPolarRadius = polarRadius;
    //    this.Calculate();
    //    pEllipsoids.Add(this);
    // }

    //private void Calculate()
    //{
    //    this.pFlattening = (this.pEquatorialRadius.ToMeters().Value - this.pPolarRadius.ToMeters().Value) / this.pEquatorialRadius.ToMeters().Value;
    //    this.pInverseFlattening = 1.0 / this.pFlattening;
    //    this.pEccentricity = Math.Sqrt((Math.Pow(this.pEquatorialRadius.Value, 2.0) - Math.Pow(this.pPolarRadius.Value, 2.0)) / Math.Pow(this.pEquatorialRadius.Value, 2.0));
    //    this.pEccentricitySquared = Math.Pow(this.Eccentricity, 2.0);
    //}




    public Spheroid(string name, double equatorialRadius, 
                    double polarRadius, bool isIvfDefinitive, 
                    DistanceUnit axisUnit)
    {
      Metadata = new Metadata(name, "", -1, "", name,"");
      SemiMajorAxis = equatorialRadius;
      SemiMinorAxis = polarRadius;
      AxisUnit = axisUnit;

      updateParameters();

      //if (isIvfDefinitive)
      //  _SemiMinorAxis = (1.0 - (1.0 / _InverseFlattening)) * _SemiMajorAxis;
      //else
      //  _SemiMinorAxis = polarRadius;


      //Spheroids.SearchEllipsoid.Add(this);
    }




    #region Ellipsoid Members


    //private double _SemiMajorAxis;

    /// <summary>
    /// Gets or sets the value of the semi-major axis.
    /// </summary>
    [DataMember (Name="SEMIMAJOR", Order = 1)]
    public double SemiMajorAxis
    { get; set; }




    //private double _SemiMinorAxis;

    /// <summary>
    /// Gets or sets the value of the semi-minor axis.
    /// </summary>
    [DataMember(Name="SEMIMINOR", Order = 2)]
    public double SemiMinorAxis
    { get; set; }




    //private DistanceUnit _AxisUnit;

    /// <summary>
    /// Gets or sets the value of the axis unit.
    /// </summary>
    [DataMember(Name = "UNIT", Order = 3)]
    public DistanceUnit AxisUnit
    { get; set; }





    /// <summary>
    /// get or set Spheroid metadata
    /// </summary>
    [DataMember(Name = "INFO", Order = 4)]
    public Metadata Metadata
    { get; set; }





    //private double _InverseFlattening;

    /// <summary>
    /// Gets or sets the value of the inverse of the flattening constant of the ellipsoid.
    /// </summary>
    public double InverseFlattening
    {
      get
      {
        //return _InverseFlattening; 
        return (SemiMajorAxis - SemiMinorAxis) / SemiMinorAxis;
      }
      //set { _InverseFlattening = value; }
    }




    //private double _InverseFlattening;

    /// <summary>
    /// Gets or sets the value of the inverse of the flattening constant of the ellipsoid.
    /// </summary>
    public double Flattening
    {
      get
      {
        //return _InverseFlattening; 
        return (SemiMajorAxis - SemiMinorAxis) / SemiMajorAxis;
      }
      //set { _InverseFlattening = value; }
    }






    /// <summary>
    /// computes vertical ellipsoid curvature radius N at given latitude
    /// </summary>
    /// <param name="latitude">given ellipsoidal latitude in radians</param>
    /// <returns>vertical ellipsoid curvature radius at given latitude</returns>
    public double FirstVerticalRadius(double latitude)
    {
      if (latitude == 0)
        return SemiMajorAxis;
      else if (latitude == Math.PI / 2)
        return SemiMajorAxis / Math.Sqrt(1 - e2);
      else
        return SemiMajorAxis / (Math.Sqrt(1 - e2 * Math.Pow(Math.Sin(latitude), 2)));
    }






    /// <summary>
    /// computes meridian ellipsoid curvature radius M at given latitude
    /// </summary>
    /// <param name="latitude">given ellipsoidal latitude in radians</param>
    /// <returns>meridian ellipsoid curvature radius at given latitude</returns>
    public double SecondMeridianRadius(double latitude)
    {
      if (latitude == 0)
        return SemiMajorAxis * (1 - e2);
      else if (latitude == Math.PI / 2)
        return SemiMajorAxis / Math.Sqrt(1 - e2);
      else
        return SemiMajorAxis * (1 - e2) / Math.Pow(1 - e2 * Math.Pow(Math.Sin(latitude), 2), 3.0/2.0);
    }






    /// <summary>
    /// computes mean curvature radius. This is often used as gauss sphere radius, 
    /// when sphere replaces ellipsoid for specific projection.
    /// </summary>
    /// <param name="latitude">given ellipsoidal latitude in radians</param>
    /// <returns>mean curvature radius for given latitude</returns>
    public double MeanCurvatureRadius(double latitude)
    {
      return Math.Sqrt(FirstVerticalRadius(latitude) * SecondMeridianRadius(latitude));
    }



    //private bool _IsIvfDefinitive;

    ///// <summary>
    ///// Tells if the Inverse Flattening is definitive for this ellipsoid. Some ellipsoids use 
    ///// the IVF as the defining value, and calculate the polar radius whenever asked. Other
    ///// ellipsoids use the polar radius to calculate the IVF whenever asked. This 
    ///// distinction can be important to avoid floating-point rounding errors.
    ///// </summary>
    //public bool IsIvfDefinitive
    //{
    //  get { return _IsIvfDefinitive; }
    //  set { _IsIvfDefinitive = value; }
    //}




    public double Eccentricity
    {
      get 
      {
        //return Math.Sqrt((Math.Pow(_SemiMajorAxis, 2.0) - Math.Pow(_SemiMinorAxis, 2.0)) / Math.Pow(_SemiMajorAxis, 2.0));
        return Math.Sqrt(1 - Math.Pow(SemiMinorAxis / SemiMajorAxis, 2.0));
      }
    }



    /// <summary>
    /// eccentricity squared
    /// </summary>
    public double e2
    {
      get 
      {
        //return (Math.Pow(SemiMajorAxis, 2.0) - Math.Pow(SemiMinorAxis, 2.0)) / Math.Pow(SemiMajorAxis, 2.0);
        return 1 - Math.Pow(SemiMinorAxis / SemiMajorAxis, 2.0);
      }
    }




    /// <summary>
    /// second eccentricity squared
    /// </summary>
    public double eII2
    {
      get
      {
        //return Math.Sqrt((Math.Pow(_SemiMajorAxis, 2.0) - Math.Pow(_SemiMinorAxis, 2.0)) / Math.Pow(_SemiMajorAxis, 2.0));
        return Math.Abs(1 - Math.Pow(SemiMajorAxis / SemiMinorAxis , 2.0));
      }
    }




    public double SecondEccentricity
    {
      get
      {
        return Math.Sqrt((Math.Pow(SemiMajorAxis, 2.0) - Math.Pow(SemiMinorAxis, 2.0)) / Math.Pow(SemiMinorAxis, 2.0));
      }
    }



    #endregion

    /// <summary>
    /// Checks whether the values of this instance is equal to the values of another instance.
    /// Only parameters used for coordinate system are used for comparison.
    /// Name, abbreviation, authority, alias and remarks are ignored in the comparison.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns>True if equal</returns>
    public bool EqualParams(object obj)
    {
      if (!(obj is Spheroid))
        return false;
      Spheroid e = obj as Spheroid;
      return (e.InverseFlattening == this.InverseFlattening &&
          /*e.IsIvfDefinitive == this.IsIvfDefinitive &&*/
          e.SemiMajorAxis == this.SemiMajorAxis &&
          e.SemiMinorAxis == this.SemiMinorAxis &&
          e.AxisUnit.EqualParams(this.AxisUnit));
    }




    private void updateParameters()
    {
      //_InverseFlattening = (_SemiMajorAxis - _SemiMinorAxis) / _SemiMajorAxis;
      //_InverseFlattening = 1.0 / _InverseFlattening;
      //_eccentricity = Math.Sqrt((Math.Pow(_SemiMajorAxis, 2.0) - Math.Pow(_SemiMinorAxis, 2.0)) / Math.Pow(_SemiMajorAxis, 2.0));
      _eccentricitySquared = Math.Pow(Eccentricity, 2.0);
    }


  }
}
